'''
此模块用于处理身份证号相关的问题。

1.验证身份证号是否有效。
2.获取身份证号码中的有效信息。
3.粗略对身份证号格式化。
3.根据身份证号计算年龄。
'''

import re
import datetime

id_pattern = re.compile(r"^([0-9]{2})([0-9]{4})([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{1})([0-9xX]{1})$")
mutil_code = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
map_code = [1,0,'X',9,8,7,6,5,4,3,2]

def id_format(idcard):
    '''对数据进行格式化。包含转换大小写，截取长度。
    '''
    idcard = idcard.strip().upper()
    return idcard[:18]

def check_code(idcard):
    sum = 0
    for i,c in enumerate(idcard[:-1]):
        sum += int(c) * mutil_code[i]
    mod = sum % 11
    return str(map_code[mod])

def id_verify(idcard):
    '''验证身份证号是否为有效的格式。简单初级验证。
    '''
    idcard = idcard.strip().upper()
    idinfo = id_pattern.match(idcard)
    if idinfo:
        check = check_code(idcard)
        if idcard[-1]!= check:
            return False
        return True
    else:
        return False

def id_verify_full(idcard):
    '''验证身份证号是否为有效的格式。包含数据校验。
    '''
    id_data = {}
    idcard = idcard.strip().upper()
    idinfo = id_pattern.match(idcard)
    if idinfo:
        #通过算法计算第18位，验证身份证号
        check = check_code(idcard)
        if idcard[-1]!= check:
            raise ValueError('id:{} 校验码出错！'.format(idcard))
        id_data['province_code'] = int(idinfo[1])
        id_data['county_code'] = int(idinfo[2])
        id_data['year'] = int(idinfo[3])
        id_data['month'] = int(idinfo[4])
        id_data['day'] = int(idinfo[5])
        id_data['region'] = int(idinfo[6])
        id_data['gender'] = int(idinfo[7])
        id_data['vercode'] = idinfo[8]
        if id_data['year'] < 1900 or id_data['year'] > 2100:
            raise ValueError('id:{} 年份[{}]错误！'.format(idcard,id_data['year']))
        if id_data['month'] <= 0 or id_data['month'] > 12:
            raise ValueError('id:{} 月份[{}]错误！'.format(idcard,id_data['month']))
        if id_data['day'] <= 0 or id_data['day'] > 31:
            raise ValueError('id:{} 生日[{}]错误！'.format(idcard,id_data['day']))
    else:
        raise ValueError('id:{} 不满足身份证号的基本格式或长度要求！'.format(idcard))
    return id_data

def age_from_datetime(date_base,date_borth):
    """根据指定基础日期，计算给出的出生日期对应的年龄
    data_base:要计算年龄的基础日期，一般为today。
    date_borth:要计算年龄的出生日期。
    return:给定的出生日期在指定基础日期时的年龄。
    """
    year_delta = date_base.year - date_borth.year
    if date_base.month < date_borth.month:
        year_delta -= 1
    elif date_base.month == date_borth.month:
        if date_base.day < date_borth.day:
            year_delta -= 1
    return year_delta

def get_age_factory(method="simple",base_date=datetime.datetime.today()):
    '''根据身份证号获取年龄。
    此函为工厂函数，数用于产生一个计算身份证号的函数，以便通过apply进行扩展计算。

    method:计算年龄的方法：
        simple:直接从身份证号中根据位置提取出生日期。
        standard:使用id_verify_full获取身份证所有信息，从中获取出生日期；带有身份证号检测校验功能。

    '''
    if not method:
        return None
    if not base_date:
        return None
    if method.lower()[0:3] == "sim":
        method = "simple"
    elif method.lower()[0:3] == "std" or method.lower()[0:3] == "sta":
        method = "standard"
    else:
        print("methond may ['simple','standard'] but give:{}.".format(method))
        return None

    def get_age(idcard):
        '''根据身份证号获取年龄。
        idcard:身份证号码。
        return:身份证号码对应出生日期计算的年龄。
        '''
        date = base_date
        if method == "simple":
            if not idcard:
                return 0
            idcard = str(idcard).strip()
            date_borth = datetime.datetime(int(idcard[6:10]),int(idcard[10:12]),int(idcard[12:14]))
        else:
            id_data = id_verify_full(idcard)
            date_borth = datetime.datetime(id_data['year'],id_data['month'],id_data['day'])
        return age_from_datetime(date,date_borth)
    return get_age

def get_age_df_factory(get_base_date,get_idcard,method="simple"):
    '''在pandas中的DataFrame中根据身份证号获取年龄。
    此函为工厂函数，数用于产生一个计算身份证号的函数，以便通过apply进行扩展计算。

    get_base_date:回调函数，用于以DataFrame中一行为参数，在此行中获取基础日期并返回。
    get_idcard:回调函数，用于以DataFrame中一行为参数，在此行中获取身份证号并返回。

    method:计算年龄的方法：
        simple:直接从身份证号中根据位置提取出生日期。
        standard:使用id_verify_full获取身份证所有信息，从中获取出生日期；带有身份证号检测校验功能。

    '''
    if not method:
        return None
    if not(get_base_date and get_idcard):
        return None
    if method.lower()[0:3] == "sim":
        method = "simple"
    elif method.lower()[0:3] == "std" or method.lower()[0:3] == "sta":
        method = "standard"
    else:
        print("methond may ['simple','standard'] but give:{}.".format(method))
        return None

    def get_age(line):
        '''在pandas中的DataFrame上根据身份证号获取年龄。
        line:每一行的迭代数据。
        return:身份证号码对应出生日期计算的年龄。
        '''
        date = get_base_date(line)
        idcard = get_idcard(line)
        if method == "simple":
            if not idcard:
                return 0
            idcard = str(idcard).strip()
            date_borth = datetime.datetime(int(idcard[6:10]),int(idcard[10:12]),int(idcard[12:14]))
        else:
            id_data = id_verify_full(idcard)
            date_borth = datetime.datetime(id_data['year'],id_data['month'],id_data['day'])
        return age_from_datetime(date,date_borth)
    return get_age


